package com.hxrfid.system.service;

import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.secure.BCrypt;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
import com.hxrfid.common.config.WeixinConfig;
import com.hxrfid.common.constant.BasicConstants;
import com.hxrfid.common.constant.CacheConstants;
import com.hxrfid.common.constant.Constants;
import com.hxrfid.common.constant.UserConstants;
import com.hxrfid.common.context.SecurityContextHolder;
import com.hxrfid.common.core.domain.dto.RoleDTO;
import com.hxrfid.common.core.domain.entity.SysUser;
import com.hxrfid.common.core.domain.model.LoginBody;
import com.hxrfid.common.core.domain.model.LoginUser;
import com.hxrfid.common.core.service.LogininforService;
import com.hxrfid.common.enums.DeviceType;
import com.hxrfid.common.enums.LoginType;
import com.hxrfid.common.enums.UserStatus;
import com.hxrfid.common.enums.UserType;
import com.hxrfid.common.exception.user.CaptchaException;
import com.hxrfid.common.exception.user.CaptchaExpireException;
import com.hxrfid.common.exception.user.UserException;
import com.hxrfid.common.helper.LoginHelper;
import com.hxrfid.common.utils.*;
import com.hxrfid.common.utils.redis.RedisUtils;
import com.hxrfid.system.domain.vo.SysTenantVo;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletRequest;
import java.time.Duration;
import java.util.*;
import java.util.function.Supplier;

/**
 * 登录校验方法
 *
 * @author Lion Li
 */
@RequiredArgsConstructor
@Slf4j
@Service
public class SysLoginService {

    private final ISysUserService userService;
    private final ISysConfigService configService;
    private final LogininforService asyncService;
    private final SysPermissionService permissionService;
    private final ISysTenantService sysTenantService;
    private final WeixinConfig weixinConfig;

    @Value("${user.password.maxRetryCount}")
    private Integer maxRetryCount;

    @Value("${user.password.lockTime}")
    private Integer lockTime;

    /**
     * 登录验证
     *
     * @param username 用户名
     * @param password 密码
     * @param code     验证码
     * @param uuid     唯一标识
     * @return 结果
     */
    public String login(String username, String password, String code, String uuid) {
        HttpServletRequest request = ServletUtils.getRequest();
        boolean captchaEnabled = configService.selectCaptchaEnabled();
        // 验证码开关
        if (captchaEnabled) {
            validateCaptcha(username, code, uuid, request);
        }
        SysUser user = loadUserByUsername(username);
        // 小程序用户不可登陆管理后台
        if (UserType.APP_USER.getUserType().equals(user.getUserType())) {
            throw new UserException("您没有登陆权限");
        }
        checkLogin(LoginType.PASSWORD, user, () -> !BCrypt.checkpw(password, user.getPassword()));
        // 此处可根据登录用户的数据不同 自行创建 loginUser
        LoginUser loginUser = buildLoginUser(user);
        // 生成token
        LoginHelper.loginByDevice(loginUser, DeviceType.PC);

        asyncService.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"), request);
        recordLoginInfo(user.getUserId(), username);
        return StpUtil.getTokenValue();
    }

    public String smsLogin(String phonenumber, String smsCode) {
        // 通过手机号查找用户
        SysUser user = loadUserByPhonenumber(phonenumber);

        HttpServletRequest request = ServletUtils.getRequest();
        checkLogin(LoginType.SMS, user, () -> !validateSmsCode(phonenumber, smsCode, request));
        // 此处可根据登录用户的数据不同 自行创建 loginUser
        LoginUser loginUser = buildLoginUser(user);
        // 生成token
        LoginHelper.loginByDevice(loginUser, DeviceType.APP);

        asyncService.recordLogininfor(user.getUserName(), Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"), request);
        recordLoginInfo(user.getUserId(), user.getUserName());
        return StpUtil.getTokenValue();
    }

    /**
     * 小程序登陆
     */
    public Map<String, Object> xcxLogin(LoginBody loginBody) {
        System.out.println("小程序登录开始" + String.format("小程序登录开始: %s", JSONUtil.toJsonStr(loginBody)));
        log.info("小程序登录参数：{}", loginBody);
        Map<String, Object> ajax = new HashMap<>();
        String code = loginBody.getCode();
        String phoneNumber = loginBody.getPhoneNumber();
        String username = loginBody.getUsername();
        // 获取openId
        String openId = getOpenId(code);
        // 查询用户是否存在
        SysUser sysUser = userService.selectUserByOpenId(openId);
        // 新增用户
        if (ObjectUtil.isNull(sysUser)) {
            sysUser = new SysUser();
            sysUser.setUserName(StringUtils.isNotBlank(username) ? username : openId);
            sysUser.setNickName(StringUtils.isNotBlank(username) ? username : "微信用户");
            sysUser.setOpenId(openId);
            sysUser.setUserType(UserType.APP_USER.getUserType());
            sysUser.setPhonenumber(phoneNumber);
            sysUser.setDeptId(BasicConstants.HUNDRED);
            sysUser.setAvatar(loginBody.getAvatar());
            sysUser.setPassword(BCrypt.hashpw("123456"));
            sysUser.setStatus(UserConstants.ROLE_NORMAL);
            sysUser.setUserFlag(BasicConstants.TWO);
            sysUser.setTenantId(TenantUtils.EMPTY_TENANT_ID);
            sysUser.setRoleIds(new Long[]{1942142675812433921L});
            sysUser.setParentId(loginBody.getParentId());
            userService.insertUser(sysUser);
        } else {
            Long parentId = loginBody.getParentId();
            sysUser.setParentId(parentId);
            userService.updateById(sysUser);
        }
        HttpServletRequest request = ServletUtils.getRequest();
        // 此处可根据登录用户的数据不同 自行创建 loginUser
        LoginUser loginUser = buildLoginUser(sysUser);
        // 生成token
        LoginHelper.loginByDevice(loginUser, DeviceType.XCX);
        asyncService.recordLogininfor(sysUser.getUserName(), Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"), request);
        recordLoginInfo(sysUser.getUserId(), sysUser.getUserName());
        ajax.put("token", StpUtil.getTokenValue());
        ajax.put("loginUser", loginUser);
        return ajax;
    }

    public String getCodeForPhone(String code) {
        return getPhone(code);
    }

    /**
     * 退出登录
     */
    public void logout() {
        try {
            LoginUser loginUser = LoginHelper.getLoginUser();
            StpUtil.logout();
            asyncService.recordLogininfor(loginUser.getUsername(), Constants.LOGOUT, MessageUtils.message("user.logout.success"), ServletUtils.getRequest());
        } catch (NotLoginException e) {
        }
    }

    /**
     * 校验短信验证码
     */
    private boolean validateSmsCode(String phonenumber, String smsCode, HttpServletRequest request) {
        String code = RedisUtils.getCacheObject(CacheConstants.CAPTCHA_CODE_KEY + phonenumber);
        if (StringUtils.isBlank(code)) {
            asyncService.recordLogininfor(phonenumber, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"), request);
            throw new CaptchaExpireException();
        }
        return code.equals(smsCode);
    }

    /**
     * 校验验证码
     *
     * @param username 用户名
     * @param code     验证码
     * @param uuid     唯一标识
     */
    public void validateCaptcha(String username, String code, String uuid, HttpServletRequest request) {
        String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + StringUtils.defaultString(uuid, "");
        String captcha = RedisUtils.getCacheObject(verifyKey);
        RedisUtils.deleteObject(verifyKey);
        if (captcha == null) {
            asyncService.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"), request);
            throw new CaptchaExpireException();
        }
        if (!code.equalsIgnoreCase(captcha)) {
            asyncService.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error"), request);
            throw new CaptchaException();
        }
    }

    private SysUser loadUserByUsername(String username) {
        SysUser user = userService.selectUserByUserName(username);
        if (ObjectUtil.isNull(user)) {
            log.info("登录用户：{} 不存在.", username);
            throw new UserException("user.not.exists", username);
        } else if (UserStatus.DELETED.getCode().equals(user.getDelFlag())) {
            log.info("登录用户：{} 已被删除.", username);
            throw new UserException("user.password.delete", username);
        } else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
            log.info("登录用户：{} 已被停用.", username);
            throw new UserException("user.blocked", username);
        }
        return user;
    }

    private SysUser loadUserByPhonenumber(String phonenumber) {
        SysUser user = userService.selectUserByPhonenumber(phonenumber);
        if (ObjectUtil.isNull(user)) {
            log.info("登录用户：{} 不存在.", phonenumber);
            throw new UserException("user.not.exists", phonenumber);
        } else if (UserStatus.DELETED.getCode().equals(user.getDelFlag())) {
            log.info("登录用户：{} 已被删除.", phonenumber);
            throw new UserException("user.password.delete", phonenumber);
        } else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
            log.info("登录用户：{} 已被停用.", phonenumber);
            throw new UserException("user.blocked", phonenumber);
        }
        return user;
    }

    /**
     * 构建登录用户
     */
    private LoginUser buildLoginUser(SysUser user) {
        LoginUser loginUser = new LoginUser();
        loginUser.setUserId(user.getUserId());
        loginUser.setUserNo(user.getUserNo());
        loginUser.setDeptId(user.getDeptId());
        loginUser.setUsername(user.getUserName());
        loginUser.setNickName(user.getNickName());
        loginUser.setUserType(user.getUserType());
        loginUser.setUserFlag(user.getUserFlag());
        loginUser.setTenantId(user.getTenantId());
        loginUser.setMenuPermission(permissionService.getMenuPermission(user));
        loginUser.setRolePermission(permissionService.getRolePermission(user));
        loginUser.setDeptName(ObjectUtil.isNull(user.getDept()) ? "" : user.getDept().getDeptName());
        List<RoleDTO> roles = BeanUtil.copyToList(user.getRoles(), RoleDTO.class);
        loginUser.setRoles(roles);
        return loginUser;
    }

    /**
     * 记录登录信息
     *
     * @param userId 用户ID
     */
    public void recordLoginInfo(Long userId, String username) {
        SysUser sysUser = new SysUser();
        sysUser.setUserId(userId);
        sysUser.setLoginIp(ServletUtils.getClientIP());
        sysUser.setLoginDate(DateUtils.getNowDate());
        sysUser.setUpdateBy(username);
        userService.updateUserProfile(sysUser);
    }

    /**
     * 登录校验
     */
    private void checkLogin(LoginType loginType, SysUser user, Supplier<Boolean> supplier) {
        if (!TenantUtils.EMPTY_TENANT_ID.equals(user.getTenantId()) && Objects.nonNull(user.getTenantId())) {
            SysTenantVo sysTenantVo = sysTenantService.queryByTenantId(user.getTenantId());
            if (null == sysTenantVo) {
                throw new UserException("user.tenant.not.exists" , user.getTenantId().toString());
            }
            if (Constants.FLAG_NO.equals(sysTenantVo.getActiveFlag())) {
                throw new UserException("user.tenant.stop" , user.getTenantId().toString());
            }
            if (DateUtils.isExpired(sysTenantVo.getActiveTime())) {
                throw new UserException("user.tenant.exceed" , user.getTenantId().toString());
            }
        }
        String username = user.getUserName();
        HttpServletRequest request = ServletUtils.getRequest();
        String errorKey = CacheConstants.PWD_ERR_CNT_KEY + SecurityContextHolder.getTenantId() + username;
        String loginFail = Constants.LOGIN_FAIL;

        // 获取用户登录错误次数(可自定义限制策略 例如: key + username + ip)
        Integer errorNumber = RedisUtils.getCacheObject(errorKey);
        // 锁定时间内登录 则踢出
        if (ObjectUtil.isNotNull(errorNumber) && errorNumber.equals(maxRetryCount)) {
            asyncService.recordLogininfor(user.getUserName(), loginFail, MessageUtils.message(loginType.getRetryLimitExceed(), maxRetryCount, lockTime), request);
            throw new UserException(loginType.getRetryLimitExceed(), maxRetryCount, lockTime);
        }

        if (supplier.get()) {
            // 是否第一次
            errorNumber = ObjectUtil.isNull(errorNumber) ? 1 : errorNumber + 1;
            // 达到规定错误次数 则锁定登录
            if (errorNumber.equals(maxRetryCount)) {
                RedisUtils.setCacheObject(errorKey, errorNumber, Duration.ofMinutes(lockTime));
                asyncService.recordLogininfor(username, loginFail, MessageUtils.message(loginType.getRetryLimitExceed(), maxRetryCount, lockTime), request);
                throw new UserException(loginType.getRetryLimitExceed(), maxRetryCount, lockTime);
            } else {
                // 未达到规定错误次数 则递增
                RedisUtils.setCacheObject(errorKey, errorNumber);
                asyncService.recordLogininfor(username, loginFail, MessageUtils.message(loginType.getRetryLimitCount(), errorNumber), request);
                throw new UserException(loginType.getRetryLimitCount(), errorNumber);
            }
        }

        // 登录成功 清空错误次数
        RedisUtils.deleteObject(errorKey);
    }

    /**
     * 获取微信openId
     * @param code
     */
    private String getOpenId(String code) {
        String url = "https://api.weixin.qq.com/sns/jscode2session";
        url += "?appid=" + weixinConfig.getAppId();
        url += "&secret=" + weixinConfig.getSecret();
        url += "&js_code=" + code;
        url += "&grant_type=authorization_code";
        url += "&connect_redirect=1";
        String response = HttpUtil.get(url);
        JSONObject jsonObject = JSONUtil.parseObj(response);
        Object errcode = jsonObject.get("errcode");
        if (ObjectUtils.isNull(errcode)) {
            return jsonObject.getStr("openid");
        } else {
            throw new UserException("登陆失败", jsonObject.get("errmsg"));
        }
    }

    /**
     * 获取登陆微信手机号
     * @param code
     */
    public String getPhone(String code) {
        String accessToken = getAccessToken(); // 需要先获取全局access_token
        String url = "https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token=" + accessToken;
        Map<String, Object> body = Collections.singletonMap("code", code);
        String post = HttpUtil.post(url, JsonUtils.toJsonString(body));
        JSONObject jsonObject = JSONUtil.parseObj(post);
        // 解析响应
        if (0 == (Integer) jsonObject.get("errcode")) {
            Map<String, Object> phoneInfo = (Map<String, Object>) jsonObject.get("phone_info");
            return phoneInfo.get("phoneNumber").toString();
        }
        throw new RuntimeException("获取手机号失败: " + jsonObject.get("errmsg"));
    }

    private String getAccessToken() {
        String url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" + weixinConfig.getAppId() + "&secret=" + weixinConfig.getSecret();
        String string = HttpUtil.get(url);
        JSONObject jsonObject = JSONUtil.parseObj(string);
        return (String) jsonObject.get("access_token");
    }
}
